/**
 * 
 */
package biz.innovasoft.nodb.impl;

import java.io.Serializable;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

import biz.innovasoft.nodb.Repository;
import biz.innovasoft.nodb.RepositoryException;
import biz.innovasoft.nodb.index.IndexException;
import biz.innovasoft.nodb.index.IndexManager;
import biz.innovasoft.nodb.index.Indexer;
import biz.innovasoft.nodb.index.Searcher;
import biz.innovasoft.nodb.query.Tuple;

/**
 * 
 * @author Diego Miranda
 *
 */
public class IndexHelper implements Serializable {

	private static final long serialVersionUID = 4802918532298085199L;
	
	private GenericRepository repository;
	private transient BlockingQueue<IndexTuple> queue;
	private transient DeferredIndexThread idxThread;
	private transient Thread thread;
	
	private static class IndexTuple implements Serializable {
	
		private static final long serialVersionUID = 5989304643162223360L;
		
		public final static int STORE = 0;
		public final static int UPDATE = 1;
		public final static int DELETE = 2;
		
		public int actionType;
		public Long oid;
		public Object value;
		
		public IndexTuple (int type, Long oid, Object value) {
			this.actionType = type;
			this.oid = oid;
			this.value = value;
		}
		
	}
	
	private static class DeferredIndexThread implements Runnable {

		private BlockingQueue<IndexTuple> queue;
		private boolean done = false;
		private IndexHelper helper;
		
		public DeferredIndexThread (IndexHelper helper, BlockingQueue<IndexTuple> queue) {
			this.helper = helper;
			this.queue = queue;
		}
		
		public void run() {
			while (true) {
				try {
					IndexTuple idxTuple = queue.take();
					process (idxTuple);
				} catch (Exception e) {
				}
			
				if (done) {
					return;
				}
			}
		}
		
		private void process (IndexTuple tuple) {
			switch (tuple.actionType) {
			case IndexTuple.STORE:
				helper.indexObject(tuple.oid, tuple.value, Repository.INDEX_YES);
				break;
			case IndexTuple.UPDATE:
				helper.reindexObject(tuple.oid, tuple.value, Repository.INDEX_YES);
				break;
			case IndexTuple.DELETE:
				helper.deleteIndexObject (tuple.oid, tuple.value, Repository.INDEX_YES);
			}
		}
		
		public void done () {
			this.done = true;
		}
		
	}

	/**
	 * 
	 * @param repository
	 */
	public IndexHelper (GenericRepository repository) {
		this.repository = repository;
	}
	
	protected void initialize () {
		this.queue = new LinkedBlockingQueue<IndexTuple>();
		this.idxThread = new DeferredIndexThread (this, queue);
		thread = new Thread (idxThread, "IdxThread");
		thread.start();
	}
	
	/*
	 * Deferred methods
	 */
	protected void reindexObjectDeferred (Long oid, Object object) {
		try {
			queue.put(new IndexTuple (IndexTuple.UPDATE, oid, object));
		} catch (Exception e) {}
	}
	
	protected void deleteIndexObjectDeferred ( Long oid, Object object ) {
		try {
			queue.put(new IndexTuple (IndexTuple.DELETE, oid, object));
		} catch (Exception e) {}
	}
	
	protected void indexObjectDeferred ( Long oid, Object object ) {
		try {
			queue.put(new IndexTuple (IndexTuple.STORE, oid, object));
		} catch (Exception e) {}
	}
	
	public void reindex() throws RepositoryException {
		
		/*
		 * Reinitialize all the indexes descriptions in the repository.
		 */
		for (Class<?> clazz : repository.getStoredClasses()) {
			IndexManager imanager = repository.getIndexManager(clazz);
			imanager.initialize();
		}
		
		/*
		 * Update all the indexes. 
		 */
		IndexManager[] managers = repository.getIndexManagers();
		for (IndexManager imanager : managers ) {
			Indexer indexer = null;
			try {
				indexer = new Indexer(imanager, false, true);
				List<Tuple> tuples = repository.getAll(imanager.getIndexClass());
				for (Tuple tuple : tuples) {
					indexer.indexObject(tuple.oid, tuple.value);
				}
			} catch (IndexException e) {
				throw new RepositoryException(e);
			} finally {
				if (indexer != null)
					indexer.close();
			}
		}
	}
	
	public void reindex(Class<?> clazz) throws RepositoryException {
		IndexManager imanager = repository.getIndexManager(clazz);
		Indexer indexer = null;
		try {
			indexer = new Indexer(imanager, true, true);
			List<Tuple> tuples = repository.getAll(clazz);
			for (Tuple tuple : tuples) {
				indexer.indexObject(tuple.oid, tuple.value);
			}
		} catch (IndexException e) {
			throw new RepositoryException(e);
		} finally {
			if (indexer != null)
				indexer.close();
		}
	}

	protected void reindex(IndexManager imanager) throws RepositoryException {
		Indexer indexer = null;
		try {
			indexer = new Indexer(imanager, true, true);
			Long[] ids = repository.getOIds();
			for (Long id : ids) {
				Object object = repository.getByOId(id);
				indexer.indexObject(id, object);
			}
		} catch (IndexException e) {
			throw new RepositoryException(e);
		} finally {
			if (indexer != null)
				indexer.close();
		}
	}

	protected void reindexObject (Long oid, Object object, int index) {
		if (index == Repository.INDEX_DEFERRED) {
			reindexObjectDeferred (oid, object);
			return;
		} else if ( index == Repository.INDEX_YES) {
			deleteIndexObject (oid, object, Repository.INDEX_YES);
			indexObject (oid, object, Repository.INDEX_YES);
		}
	}
	
	protected void deleteIndexObject (Long oid, Object object) {
		deleteIndexObjectDeferred(oid, object);
	}
	
	protected void deleteIndexObject (Long oid, Object object, int index) {
		if ( index == Repository.INDEX_DEFERRED) {
			deleteIndexObjectDeferred(oid, object);
			return;
		} else if ( index == Repository.INDEX_YES) {
			/* Delete reference from the object index */
			IndexManager imanager = repository.getIndexManager(object.getClass());
			Searcher searcher = new Searcher(imanager);
			searcher.deleteIndex(oid);
		}
	}
	
	protected void indexObject(Long oid, Object object, int index)
			throws RepositoryException {
		if ( index == Repository.INDEX_DEFERRED) {
			indexObjectDeferred(oid, object);
			return;
		} else if ( index == Repository.INDEX_YES) {
			IndexManager imanager = repository.getIndexManager(object.getClass());
			Indexer indexer = null;
			try {
				indexer = new Indexer(imanager, false, false);
				indexer.indexObject(oid, object);
			} catch (IndexException e) {
				throw new RepositoryException(e);
			} finally {
				indexer.close();
			}
		}
	}
	
	public void close () {
		this.idxThread.done();
	}
	
	
	
}
